#include "StdAfx.h"
#include "..\sgp\ClockManager.h"
#include "..\sgp\MouseRegionManager.h"

namespace ja2{ namespace sgp
{
	namespace
	{
		//! In timer ticks.
		/*!
			--REFERENCE--: gsFastHelpDelay
		*/
		std::uint_least16_t const fast_help_delay = 600;
		//! Max double click delay (in milliseconds) to be considered a double
		// click.
		/*!
			--REFERENCE--: MSYS_DOUBLECLICK_DELAY
		*/
		std::uint_least16_t const double_click_delay = 400;
	}
	MouseRegionManager::MouseRegionManager(ClockManager &ClockManager_)
		: m_ClockManager(ClockManager_),
		m_CurrentMX(0),
		m_CurrentMY(0),
		m_CurrentButtons(0),
		m_Action(A_MSYS_NO_ACTION),
		m_UseMouseHandlerHook(true),
		m_GrabRegion(m_MouseRegionList.end()),
		m_RefreshUpdate(false),
		m_CurrentRegion(m_MouseRegionList.end()),
		m_PrevRegion(m_MouseRegionList.end()),
		m_ClickedModeOn(false),
		m_ClickedRegion(m_MouseRegionList.end()),
		m_RegionLastLButtonDown(m_MouseRegionList.end()),
		m_RegionLastLButtonUp(m_MouseRegionList.end()),
		m_RegionLastLButtonDownTime(0)
	{
		JA2_REGISTER_DEBUG_TOPIC(TOPIC_MOUSE_SYSTEM, "Mouse Region System");

		// Setup the system's background region
//x		MSYS_SystemBaseRegion.Cursor							= 0;
//x		MSYS_SystemBaseRegion.UserData[0]					= 0;
//x		MSYS_SystemBaseRegion.UserData[1]					= 0;
//x		MSYS_SystemBaseRegion.UserData[2]					= 0;
//x		MSYS_SystemBaseRegion.UserData[3]					= 0;
//x		MSYS_SystemBaseRegion.FastHelpRect				= -1;
		// Add the base region to the list
		add_region(MouseRegionPtr_t(new MouseRegion(
			MouseRegion::MSYS_PRIORITY_SYSTEM, MouseRegion::F_MSYS_REGION_ENABLED | MouseRegion::F_MSYS_SET_CURSOR, -32767, -32767, 32767, 32767, Point(), Point(), 0, TT_OVERHEAD, std::string()
			))
		);
		// Save system region
		auto end = invalid_region();
		m_SystemRegion =  --end;
	}

	MouseRegionManager::~MouseRegionManager()
	{
		JA2_UNREGISTER_DEBUG_TOPIC(TOPIC_MOUSE_SYSTEM, "Mouse Region System");
	}
	

	void MouseRegionManager::add_region(MouseRegionPtr_t const &pRegion)
	{
		auto it = m_MouseRegionList.begin();
		// Walk down list until we find place to insert (or at end of list)
		for(auto end = m_MouseRegionList.end(); it != end && !((*it)->priority() >= pRegion->priority()); ++it)
		{
		}
		m_MouseRegionList.insert(it, pRegion);
	}

	void MouseRegionManager::mouse_handler_hook(InputAtom::EventType Type, std::uint_least16_t XCoord, std::uint_least16_t YCoord, bool LeftButton, bool RightButton)
	{
		// Only if using hook
		if(!m_UseMouseHandlerHook)
			return;

		m_Action = A_MSYS_NO_ACTION;
		switch(Type)
		{
		case InputAtom::ET_LEFT_BUTTON_DOWN:
		case InputAtom::ET_LEFT_BUTTON_UP:
		case InputAtom::ET_RIGHT_BUTTON_DOWN:
		case InputAtom::ET_RIGHT_BUTTON_UP:
			if(Type == InputAtom::ET_LEFT_BUTTON_DOWN)
				m_Action |= A_MSYS_DO_LBUTTON_DWN;
			else if(Type == InputAtom::ET_LEFT_BUTTON_UP)
			{
				m_Action |= A_MSYS_DO_LBUTTON_UP;
				//Kris:
				//Used only if applicable.	This is used for that special button that is locked with the
				//mouse press -- just like windows.	When you release the button, the previous state
				//of the button is restored if you released the mouse outside of it's boundaries.	If
				//you release inside of the button, the action is selected -- but later in the code.
				//NOTE:	It has to be here, because the mouse can be released anywhere regardless of
				//regions, buttons, etc.
//x				ReleaseAnchorMode();
			}
			else if(Type == InputAtom::ET_RIGHT_BUTTON_DOWN)
				m_Action |= A_MSYS_DO_RBUTTON_DWN;
			else if(Type == InputAtom::ET_RIGHT_BUTTON_UP)
				m_Action |= A_MSYS_DO_RBUTTON_UP;
			if(LeftButton)
				m_CurrentButtons |= BM_MSYS_LEFT_BUTTON;
			else
				m_CurrentButtons &= ~BM_MSYS_LEFT_BUTTON;

			if(RightButton)
				m_CurrentButtons |= BM_MSYS_RIGHT_BUTTON;
			else
				m_CurrentButtons &= (~BM_MSYS_RIGHT_BUTTON);

			if((XCoord != m_CurrentMX) || (YCoord != m_CurrentMY))
			{
				m_Action |= A_MSYS_DO_MOVE;
				m_CurrentMX = XCoord;
				m_CurrentMY = YCoord;
			}
			update_mouse_region();
			break;
			// ATE: Checks here for mouse button repeats.....
			// Call mouse region with new reason
		case InputAtom::ET_LEFT_BUTTON_REPEAT:
		case InputAtom::ET_RIGHT_BUTTON_REPEAT:
			if(Type == InputAtom::ET_LEFT_BUTTON_REPEAT)
				m_Action |= A_MSYS_DO_LBUTTON_REPEAT;
			else if(Type == InputAtom::ET_RIGHT_BUTTON_REPEAT)
				m_Action |= A_MSYS_DO_RBUTTON_REPEAT;
			if((XCoord != m_CurrentMX) || (YCoord != m_CurrentMY))
			{
				m_Action |= A_MSYS_DO_MOVE;
				m_CurrentMX = XCoord;
				m_CurrentMY = YCoord;
			}
			update_mouse_region();
			break;
		case InputAtom::ET_MOUSE_POS:
			if((XCoord != m_CurrentMX) || (YCoord != m_CurrentMY) || m_RefreshUpdate)
			{
				m_Action |= A_MSYS_DO_MOVE;
				m_CurrentMX = XCoord;
				m_CurrentMY = YCoord;

				m_RefreshUpdate = false;

				update_mouse_region();
			}
			break;
		default:
			JA2_DEBUG_MESSAGE(TOPIC_MOUSE_SYSTEM, DebugManager::DL_0, "ERROR -- MSYS 2 SGP Mouse Hook got bad type");
			break;
		}
	}

	bool MouseRegionManager::current_region_valid() const
	{
		return (m_CurrentRegion != m_MouseRegionList.end());
	}

	bool MouseRegionManager::is_action_set(Action Action_) const
	{
		return (m_Action & Action_) != 0;
	}

	void MouseRegionManager::unset_action(Action Action_)
	{
		m_Action &= ~Action_;
	}

	void MouseRegionManager::update_mouse_region()
	{
		bool found = false;

		// WANNE: Error handling Bugfix by birdflu (to avoid Stack Overflow)
//x		if(guiCurrentScreen == ERROR_SCREEN)
//x		{
//x			return;
//x		}

		// Check previous region!
		if(m_GrabRegion != invalid_region())
		{
			m_CurrentRegion = m_GrabRegion;
			found = true;
		}
		else
			m_CurrentRegion = m_MouseRegionList.begin();
		// Find the region in which mouse is now
		for(auto it = m_CurrentRegion, end = m_MouseRegionList.end(); it != end; ++it)
		{
			MouseRegion &actual_region = *(*it);
			// Check boundaries
			if(actual_region.is_flag_set(MouseRegion::F_MSYS_REGION_ENABLED) &&
				actual_region.is_flag_set(MouseRegion::F_MSYS_ALLOW_DISABLED_FASTHELP) &&
				actual_region.is_inside(m_CurrentMX, m_CurrentMY, m_CurrentMX, m_CurrentMY)
			)
			{
				// We got the right region. We don't need to check for priorities 'cause
				// the whole list is sorted the right way!
				found = true;
				break;
			}
		}

		// Previous region is valid
		if(m_PrevRegion != invalid_region())
		{
			// Unset that we are in
			(*m_PrevRegion)->unset_flag(MouseRegion::F_MSYS_MOUSE_IN_AREA);
			// Not the current region
			if (m_PrevRegion != m_CurrentRegion)
			{
				//Remove the help text for the previous region if one is currently being displayed.
				if(!(*m_PrevRegion)->fast_help_text().empty())
				{
					//ExecuteMouseHelpEndCallBack( MSYS_PrevRegion );
#ifdef _JA2_RENDER_DIRTY
//x					if( m_PrevRegion->is_flag_set(F_MSYS_GOT_BACKGROUND))
//x						FreeBackgroundRectPending( MSYS_PrevRegion->FastHelpRect );
#endif
					(*m_PrevRegion)->unset_flag(MouseRegion::F_MSYS_GOT_BACKGROUND);
					(*m_PrevRegion)->unset_flag(MouseRegion::F_MSYS_FASTHELP_RESET);

#ifndef JA2			
//x					VideoRemoveToolTip();
#endif
				}
				if(current_region_valid())
					(*m_CurrentRegion)->fast_help_timer(fast_help_delay);
				// Force a callbacks to happen on previous region to indicate that
				// the mouse has left the old region
				if((*m_PrevRegion)->is_flag_set(MouseRegion::F_MSYS_MOVE_CALLBACK) && (*m_PrevRegion)->is_flag_set(MouseRegion::F_MSYS_REGION_ENABLED))
					(*m_PrevRegion)->movement_callback(MouseRegion::CR_MSYS_CALLBACK_REASON_LOST_MOUSE);
			}
		}

		// If a region was found in the list, update it's data
		if(found)
		{
			if(m_CurrentRegion != m_PrevRegion)
			{
				//Kris -- October 27, 1997
				//Implemented gain mouse region
				if((*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_MOVE_CALLBACK ))
				{
					if(!(*m_CurrentRegion)->fast_help_text().empty() && !(*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_FASTHELP_RESET ))
					{
						(*m_CurrentRegion)->fast_help_timer(fast_help_delay);
#ifdef _JA2_RENDER_DIRTY
//x						if( MSYS_CurrRegion->uiFlags & MSYS_GOT_BACKGROUND )
//x							FreeBackgroundRectPending( MSYS_CurrRegion->FastHelpRect );
#endif
						(*m_CurrentRegion)->unset_flag(MouseRegion::F_MSYS_GOT_BACKGROUND);
						(*m_CurrentRegion)->set_flag(MouseRegion::F_MSYS_FASTHELP_RESET);
#ifndef JA2			
//x						VideoRemoveToolTip();
#endif

						//if( b->uiFlags & BUTTON_ENABLED )
						//	b->uiFlags |= BUTTON_DIRTY;
					}
					if((*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_REGION_ENABLED))
						(*m_CurrentRegion)->movement_callback(MouseRegion::CR_MSYS_CALLBACK_REASON_GAIN_MOUSE);
				}
				// if the cursor is set and is not set to no cursor
				if((*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_REGION_ENABLED) && (*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_SET_CURSOR) && (*m_CurrentRegion)->is_cursor_set())
				{
//x					MSYS_SetCurrentCursor(MSYS_CurrRegion->Cursor);
				}
				else
				{
					// Addition Oct 10/1997 Carter, patch for mouse cursor
					// start at region and find another region encompassing
					for(MouseRegionList_t::iterator it = ++m_CurrentRegion, end = m_MouseRegionList.end(); it != end; ++it)
					{
						// Found
						if((*it)->is_flag_set(MouseRegion::F_MSYS_REGION_ENABLED) &&
							(*it)->is_inside(m_CurrentMX, m_CurrentMY, m_CurrentMX, m_CurrentMY) &&
							(*it)->is_flag_set(MouseRegion::F_MSYS_SET_CURSOR)
							)
						{
							if((*it)->is_cursor_set())
							{
//x								MSYS_SetCurrentCursor(pTempRegion->Cursor);
							}
							break;
						}
					}
				}
			}

			// OK, if we do not have a button down, any button is game!
			if (!m_ClickedModeOn || (m_ClickedModeOn && m_ClickedRegion == m_CurrentRegion))
			{
				(*m_CurrentRegion)->set_flag(MouseRegion::F_MSYS_MOUSE_IN_AREA);
				(*m_CurrentRegion)->set_mouse_position(Point(m_CurrentMX, m_CurrentMY));

				(*m_CurrentRegion)->set_button_state_all(m_CurrentButtons);

				if((*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_REGION_ENABLED) &&
					(*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_MOVE_CALLBACK) && is_action_set(A_MSYS_DO_MOVE))
				{
					(*m_CurrentRegion)->movement_callback(MouseRegion::CR_MSYS_CALLBACK_REASON_MOVE);
				}

				//ExecuteMouseHelpEndCallBack( MSYS_CurrRegion );
				//MSYS_CurrRegion->FastHelpTimer = gsFastHelpDelay;
				
				unset_action(A_MSYS_DO_MOVE);

				if((*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_BUTTON_CALLBACK) && is_action_set(A_MSYS_DO_BUTTONS))
				{
					if((*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_REGION_ENABLED))
					{
						std::uint_least32_t button_reason = MouseRegion::CR_MSYS_CALLBACK_REASON_NONE;
						if(is_action_set(A_MSYS_DO_LBUTTON_DWN) )
						{
							button_reason |= MouseRegion::CR_MSYS_CALLBACK_REASON_LBUTTON_DWN;
							m_ClickedModeOn = true;
							// Set current region
							m_ClickedRegion = m_CurrentRegion;
						}

						if(is_action_set(A_MSYS_DO_LBUTTON_UP))
						{
							button_reason |= MouseRegion::CR_MSYS_CALLBACK_REASON_LBUTTON_UP;
							m_ClickedModeOn = false;
						}

						if(is_action_set(A_MSYS_DO_RBUTTON_DWN))
						{
							button_reason |= MouseRegion::CR_MSYS_CALLBACK_REASON_RBUTTON_DWN;
							m_ClickedModeOn = true;
							// Set global ID
							m_ClickedRegion = m_CurrentRegion;
						}

						if(is_action_set(A_MSYS_DO_RBUTTON_UP))
						{
							button_reason |= MouseRegion::CR_MSYS_CALLBACK_REASON_RBUTTON_UP;
							m_ClickedModeOn = false;
						}
						// ATE: Added repeat resons....
						if(is_action_set(A_MSYS_DO_LBUTTON_REPEAT))
							button_reason |= MouseRegion::CR_MSYS_CALLBACK_REASON_LBUTTON_REPEAT;


						if(is_action_set(A_MSYS_DO_RBUTTON_REPEAT))
							button_reason |= MouseRegion::CR_MSYS_CALLBACK_REASON_RBUTTON_REPEAT;

						if( button_reason != MouseRegion::CR_MSYS_CALLBACK_REASON_NONE )
						{
							if((*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_FASTHELP))
							{
								// Button was clicked so remove any FastHelp text
								(*m_CurrentRegion)->unset_flag(MouseRegion::F_MSYS_FASTHELP);
#ifdef _JA2_RENDER_DIRTY
//xx								if( MSYS_CurrRegion->uiFlags & MSYS_GOT_BACKGROUND )
//xx									FreeBackgroundRectPending( MSYS_CurrRegion->FastHelpRect );
#endif
								(*m_CurrentRegion)->unset_flag(MouseRegion::F_MSYS_GOT_BACKGROUND);

								(*m_CurrentRegion)->fast_help_timer(fast_help_delay);
								(*m_CurrentRegion)->unset_flag(MouseRegion::F_MSYS_FASTHELP_RESET);

#ifndef JA2
//x								VideoRemoveToolTip();
#endif
							}

							//Kris: Nov 31, 1999 -- Added support for double click events.
							//This is where double clicks are checked and passed down.
							if( button_reason == MouseRegion::CR_MSYS_CALLBACK_REASON_LBUTTON_DWN )
							{
								std::uint_least32_t current_time = m_ClockManager.clock();
								// Sequential left click on same button within the maximum time allowed for a double click
								// Double click check succeeded, set flag and reset double click globals.
								if(m_RegionLastLButtonDown == m_CurrentRegion &&
									m_RegionLastLButtonUp == m_CurrentRegion &&
									current_time <= m_RegionLastLButtonDownTime + double_click_delay)
								{
									
									button_reason |= MouseRegion::CR_MSYS_CALLBACK_REASON_LBUTTON_DOUBLECLICK;

									m_RegionLastLButtonDown = invalid_region();
									m_RegionLastLButtonUp = invalid_region();
									m_RegionLastLButtonDownTime = 0;
								}
								// First click, record time and region pointer (to check if 2nd click detected later)
								else 
								{ 
									m_RegionLastLButtonDown = m_CurrentRegion;
									m_RegionLastLButtonDownTime = m_ClockManager.clock();
								}
							}
							else if( button_reason == MouseRegion::CR_MSYS_CALLBACK_REASON_LBUTTON_UP )
							{
								std::uint_least32_t current_time = m_ClockManager.clock();
								// Double click is Left down, then left up, then left down. We have just detected the left up here (step 2).
								if( m_RegionLastLButtonDown == m_CurrentRegion && current_time <= m_RegionLastLButtonDownTime + double_click_delay)
									m_RegionLastLButtonUp = m_CurrentRegion;
								// User released mouse outside of current button, so kill any chance of a double click happening.
								else 
								{
									m_RegionLastLButtonDown = invalid_region();
									m_RegionLastLButtonUp = invalid_region();
									m_RegionLastLButtonDownTime = 0;
								}
							}
							(*m_CurrentRegion)->button_callback(button_reason);
						}
					}
				}
				unset_action(A_MSYS_DO_BUTTONS);
			}
			else if((*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_REGION_ENABLED))
			{
				// OK here, if we have release a button, UNSET LOCK wherever you are....
				// Just don't give this button the message....
				if(is_action_set(A_MSYS_DO_RBUTTON_UP))
					m_ClickedModeOn = false;
				if(is_action_set(A_MSYS_DO_LBUTTON_UP))
					m_ClickedModeOn = false;
				// OK, you still want move messages however....
				(*m_CurrentRegion)->set_flag(MouseRegion::F_MSYS_MOUSE_IN_AREA);
				(*m_CurrentRegion)->set_mouse_position(Point(m_CurrentMX, m_CurrentMY));

				if((*m_CurrentRegion)->is_flag_set(MouseRegion::F_MSYS_MOVE_CALLBACK) && (is_action_set(A_MSYS_DO_MOVE)))
					(*m_CurrentRegion)->movement_callback(MouseRegion::CR_MSYS_CALLBACK_REASON_MOVE);

				unset_action(A_MSYS_DO_MOVE);
			}
			m_PrevRegion = m_CurrentRegion;
		}
		else
			m_PrevRegion = invalid_region();
	}

	bool MouseRegionManager::region_in_list(MouseRegionPtr_t const &pRegion) const
	{
		return (std::find(m_MouseRegionList.begin(), m_MouseRegionList.end(), pRegion) != m_MouseRegionList.end());
	}

	void MouseRegionManager::delete_region_from_list(MouseRegionPtr_t const &pRegion)
	{
		// Check if region in list
		auto it = std::find(m_MouseRegionList.begin(), m_MouseRegionList.end(), pRegion);
		if(it != invalid_region())
		{
			
			// Did we delete a grabbed region?
			if(m_GrabRegion != invalid_region() && m_GrabRegion == it)
				m_GrabRegion = invalid_region();
			// Remove a node from the list
			m_MouseRegionList.erase(it);
		}
	}

	MouseRegionManager::MouseRegionList_t::const_iterator MouseRegionManager::invalid_region() const
	{
		return m_MouseRegionList.end();
	}

	MouseRegionManager::MouseRegionList_t::iterator MouseRegionManager::invalid_region()
	{
		return m_MouseRegionList.end();
	}
}/*sgp*/} /*ja2*/